/**
 * 
 */
package com.zte.claa.inficombo.csa.app.service.correction.impl;



import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.zte.claa.inficombo.csa.app.config.LocGpsCorrectionConfig;
import com.zte.claa.inficombo.csa.app.dao.mysql.impl.LocCorrectionConfigMapper;
import com.zte.claa.inficombo.csa.app.model.redis.DevMeteData;
import com.zte.claa.inficombo.csa.app.service.correction.LocCorrectionService;



/**
 * gps纠偏服务
 * correctionWithDevMap :存储配置了信标的纠偏坐标
 * correctionWithoutDevMap :存储手动打点的纠偏坐标
 * 
 * @author 0283000170
 *
 */
@Service
public class LocCorrectionServiceImpl implements LocCorrectionService, InitializingBean, DisposableBean{
	private static final Logger logger = LoggerFactory.getLogger(LocCorrectionServiceImpl.class);
	
	@Value("${csa.coordinate.correcte.grouplen}")
	private Double groupLen = 60.0;
		
	private static final String ON = "ON";
	
	private static final String OFF = "OFF";
	
	@Value("${csa.coordinate.correct.switchon}")
	private boolean needCorrect = true;
	
	//纠偏区域最大获取次数
	private static final Integer maxGetGroupNum = 10;  
	
	//gw的lora信号强度是否需要发布
	private boolean needPublish = false;

	/*
	 * gps纠偏map
	 * deveui有值
	 */
	private Map<String, Map<String,LocGpsCorrectionConfig>> correctionWithDevMap = new ConcurrentHashMap<String, Map<String,LocGpsCorrectionConfig>>();	
	/*
	 * gps纠偏map
	 * deveui无值
	 */
	private Map<String, Map<String,List<LocGpsCorrectionConfig>>> correctionWithoutDevMap = new ConcurrentHashMap<String, Map<String,List<LocGpsCorrectionConfig>>>();
		
	
	@Autowired
	private LocCorrectionConfigMapper mapper;
	
	@Override
	public List<LocGpsCorrectionConfig> selectAll()throws Exception{
		return mapper.selectAll();
	}
	
	@Override
	public List<LocGpsCorrectionConfig> selectProject() throws Exception{
		return mapper.selectProject();
	}
	 
	@Override 
	public List<LocGpsCorrectionConfig> selectWithDeveui(String projectid)throws Exception{
		return mapper.selectWithDeveui(projectid);
	}
	
	@Override
	public List<LocGpsCorrectionConfig> selectWithoutDeveui(String projectid)throws Exception{
		return mapper.selectWithoutDeveui(projectid);
	}
	

	@Override
	public void reload() throws Exception{		
		loadConfig();
	}	
	
	@Override
	public void afterPropertiesSet() throws Exception{	
		// 启动时默认纠偏功能是关闭的
		// this.needCorrect = false;
		logger.info("needCorrect = {}.", this.needCorrect);		
		new Thread(()->loadConfig()).start();		
	}	
	
	@Override
	public void destroy() throws Exception{	
		correctionWithDevMap.clear();
		correctionWithoutDevMap.clear();
	}
	
	
	/**
	 * 
	 * 是否开启gps纠偏功能
	 */
	public void switchCorrection(String sw) throws Exception{
		if(sw == null || sw.trim().equals("")){
			return;
		}
		if(ON.equalsIgnoreCase(sw.trim())){			
			this.needCorrect = true;
			logger.info("correction is opened");			
		}else if(OFF.equalsIgnoreCase(sw.trim())){			
			this.needCorrect = false;
			logger.info("correction is closed");
		}		
	}	
	
	//加载纠偏配置信息
	private void loadConfig(){
		List<LocGpsCorrectionConfig> projects = null; 
		try{
			projects = selectProject();
			logger.info("load LocCorrectionService projectid :{}" ,projects);
		}catch (Exception e) {
			correctionWithDevMap.clear();
			correctionWithoutDevMap.clear();
			logger.error("load LocCorrectionService fail msg:{}" ,e.getMessage());
			return;
		}
		
		if(projects != null &&  projects.size() != 0){			
			loadConfigWithDev(projects);
			loadConfigWithoutDev(projects);		
		}else{
			correctionWithDevMap.clear();
			correctionWithoutDevMap.clear();
			logger.warn("CorrectionConfig project is null");
			return;
		}
	}

	
	//加载信标纠偏
	private void loadConfigWithDev(List<LocGpsCorrectionConfig> projects){
		if(projects == null || projects.size() == 0)
			return;
				
		for(LocGpsCorrectionConfig prj : projects){		
			try{
				//配置信标纠偏,这种配置点不会太多,一次查询加载
				List<LocGpsCorrectionConfig> configs = new ArrayList<LocGpsCorrectionConfig>();
				configs = selectWithDeveui(prj.getProjectId());
				if (configs != null && configs.size() != 0){
					//数据扁平化,方便gps纠偏时直接获取
					Map<String, LocGpsCorrectionConfig> map = new ConcurrentHashMap<String, LocGpsCorrectionConfig>();					
					for(LocGpsCorrectionConfig config : configs){
						map.put(config.getDeveui(), config);
					}					
					correctionWithDevMap.put(prj.getProjectId(), map);						
				}else{
					correctionWithDevMap.remove(prj.getProjectId());
				}
			}catch (Exception e) {
				correctionWithDevMap.remove(prj.getProjectId());
				logger.error("CorrectionConfig load config fail");
			}
			logger.info("CorrectionConfig load with deveui config is ok");
		}
	}	
	
	
	//加载坐标纠偏
	private void loadConfigWithoutDev(List<LocGpsCorrectionConfig> projects){
		if(projects == null || projects.size() == 0)
			return;	
		
		int batch = 500;
		int offset = 0;
		int cc = 0;
		boolean hasMore = true;	
		
		for(LocGpsCorrectionConfig prj : projects){	
			//取坐标纠偏,配置点很多,多次查询加载	
			try{
				List<LocGpsCorrectionConfig> cfgs = new ArrayList<LocGpsCorrectionConfig>();
				while(hasMore){	
					List<LocGpsCorrectionConfig> list = this.mapper.selectWithoutDeveuiByBatch(prj.getProjectId(), batch, offset);
					if(list == null || list.size() == 0){
						hasMore = false;
						continue;
					}					
					cc++;
					offset = cc * batch;
					if(list.size() < batch){
						hasMore = false;
					}						
					cfgs.addAll(list);
				}
				
				if (cfgs != null && cfgs.size() != 0){
					Map<String, List<LocGpsCorrectionConfig>> map = new ConcurrentHashMap<String, List<LocGpsCorrectionConfig>>();
					
					for(LocGpsCorrectionConfig cfg : cfgs){
						String key = this.getKey(cfg);
						if(map.get(key) == null){
							List<LocGpsCorrectionConfig> list = new ArrayList<LocGpsCorrectionConfig>();
							list.add(cfg);
							map.put(key,list);
						}else{
							map.get(key).add(cfg);
						}
					}	
					logger.info("CorrectionConfig load without deveui continue key num is" + map.keySet().size());
					correctionWithoutDevMap.put(prj.getProjectId(), map);	
					logger.info("CorrectionConfig load without deveui config is ok");
				}else{
					correctionWithoutDevMap.remove(prj.getProjectId());
				}
			}catch (Exception e) {
				correctionWithoutDevMap.remove(prj.getProjectId());
				logger.error("CorrectionConfig load config fail");
			}
			logger.info("CorrectionConfig load without deveui config is ok");					
		}			
	}
	
	
	private String getKey(LocGpsCorrectionConfig config){
		
		return config.getX1().toString() + "," + config.getY1().toString();		
	}
	

	//gps纠偏入口方法
	public void correctGps(List<DevMeteData> dataList) throws Exception{
		if(!needCorrect) return;
		
		if (dataList == null || dataList.size() == 0){
			return ;
		} 
		
		long startTime = System.currentTimeMillis();   //获取开始时间

		for(DevMeteData data : dataList){			
			dispatcherCorrection(data);
		}
		
		long endTime = System.currentTimeMillis();    //获取结束时间		
		logger.info("correct gps spend time: " + (endTime - startTime) + "ms");		
	}
	

	//纠偏分发给不同流程
	private void dispatcherCorrection(DevMeteData mdata){
		if(mdata == null)
			return;
				
		Map<String, Object> gpsData = mdata.getMdata();	
		
		//如果gps中带有蓝牙信标数据,终端会将信标的信号强度排序处理,gw1最强		
		String beaconEui = (String)gpsData.get("gw1");
		if(beaconEui != null && beaconEui.trim().length() != 0){						
			correctGpsWithDev(gpsData, mdata);

		}else{						
			correctGpsWithoutDev(gpsData, mdata);

		}	
	}
	
	//终端坐标替换方式的纠偏
	private void correctGpsWithDev(Map<String, Object> gpsData, DevMeteData mdata){	
		if(mdata == null){
			logger.warn("metedata is null!");
			return;
		}
		LocGpsCorrectionConfig config = null; 
		String projectId = mdata.getProjectid();
		Map<String,LocGpsCorrectionConfig> confMap = this.correctionWithDevMap.get(projectId);
		if(confMap != null){
			String beaconEui = (String)gpsData.get("gw1");	
			logger.debug("correctGpsWithDev beaconEui is:{}",beaconEui);
			config = confMap.get(beaconEui);
			
			if (config != null){
				gpsData.put("lon", Double.toString(config.getX0()));
				gpsData.put("lat", Double.toString(config.getY0()));			
			}else{
				logger.warn("correctGpsWithDev has no config,deveui is:{}, gpsData is:{}",mdata.getDeveui(), gpsData.toString());
				//找不到蓝牙信标的纠偏坐标,就用手动配置的纠偏坐标
				correctGpsWithoutDev(gpsData, mdata);
			}
		}else{
			//找不到蓝牙信标的纠偏坐标,就用手动配置的纠偏坐标
			correctGpsWithoutDev(gpsData, mdata);
		}			
	}
	
	
	//最近距离坐标点的纠偏
	private void correctGpsWithoutDev(Map<String, Object> gpsData, DevMeteData mdata){	
		
		logger.debug("correctGpsWithoutDev deveui is:{}, gpsData is:{}",mdata.getDeveui(), gpsData.toString());
		
		List<DistanceInfo> distanceList = null;
		DistanceInfo distanceInfo = null;
		//遍历计算距离
		distanceList = getAllDistanceList(gpsData,mdata);
		
		if(distanceList != null && distanceList.size() != 0){			
			//获取最短的记录的点
			distanceInfo = getMinDistancePoint(distanceList);
			
			if(distanceInfo != null){				
				//logger.debug("correctGpsWithoutDev before correct Coordinate -> dev:{}, gpsData is:{}",mdata.getDeveui(),gpsData);				
				gpsData.put("lon", Double.toString(distanceInfo.getConfig().getX0()));
				gpsData.put("lat", Double.toString(distanceInfo.getConfig().getY0()));				
				//logger.debug("correctGpsWithoutDev after correct Coordinate -> dev:{}, gpsData is:{}",mdata.getDeveui(),gpsData);				
			}else{
				logger.warn("correctGpsWithoutDev has no distance info ,deveui is:{}, gpsData is:{}",mdata.getDeveui(), gpsData.toString());
			}			
		}else{
			logger.warn("correctGpsWithoutDev the distanceList is null,deveui is:{}, gpsData is:{}",mdata.getDeveui(), gpsData.toString());
			return ;
		}					
	}
	

	
	private List<DistanceInfo> getAllDistanceList(Map<String, Object> gpsData,DevMeteData mdata){
		
		List<String> goupList = null;
		List<LocGpsCorrectionConfig> pointConfigList = null;
		List<DistanceInfo> distanceList = null;
		
		for(int i = 0; i < maxGetGroupNum; i++){						
			try{
				goupList = getGroup(gpsData, i + 1);
				//分组不存在继续循环
				if(goupList == null || goupList.size() == 0){
					continue;
				}	
				
				pointConfigList = getPointListByGroup(gpsData, mdata, goupList);
				//配置点不存在继续循环
				if(pointConfigList == null || pointConfigList.size() == 0){					
					continue;					
				}else{
					//找到配置点,跳出循环
					distanceList = getDistanceList(gpsData,mdata, pointConfigList);	
					logger.info("getAllDistanceList get distance list ,count: {}", i);
					break;
				}
				 			
			}catch (Exception e) {
				logger.error("countDistanceList is error, msg:",e.getMessage());
				continue;
			}
		}	
		return distanceList;	
	}
	
	
	//通过groupkey获取组内的所有配置点
	private List<LocGpsCorrectionConfig> getPointListByGroup(Map<String, Object> gpsData,DevMeteData mdata,List<String> groupList) {	
		//分组不存在就退出
		if(groupList == null || groupList.size() == 0){
			logger.info("getPointListByGroup the groupList is empty,deveui:{}",mdata.getDeveui());
			return null;			
		}
		
		String projectid = mdata.getProjectid();
		if(projectid == null || projectid.trim().length() == 0)
			return null;
		
		Map<String,List<LocGpsCorrectionConfig>> map = this.correctionWithoutDevMap.get(projectid);	
		if(map == null)
			return null;
								
		List<LocGpsCorrectionConfig> pointConfigList = new ArrayList<LocGpsCorrectionConfig>();
		
		for(String groupKey : groupList){		
			List<LocGpsCorrectionConfig> list = map.get(groupKey);
			if(list != null && list.size() != 0){
				pointConfigList.addAll(list);
			}			
		}	
		return pointConfigList;
	}
	
	
	
	
	//通获取距离列表
	private List<DistanceInfo> getDistanceList(Map<String, Object> gpsData,DevMeteData mdata, List<LocGpsCorrectionConfig> pointConfigList){
		//点列表不存在就退出
		if(pointConfigList == null || pointConfigList.size() == 0){
			logger.info("getDistanceList the listConfig is empty ,deveui:{}",mdata.getDeveui());
			return null;			
		}
			
		List<DistanceInfo> listDistance = new ArrayList<DistanceInfo>();
		
		for(LocGpsCorrectionConfig config : pointConfigList){		
			Double ret = getDistance(config, gpsData);
			if(ret.equals(-1.0)) continue;
			
			DistanceInfo distanceInfo = new DistanceInfo();
			distanceInfo.setDistance(ret);
			distanceInfo.setConfig(config);			
			listDistance.add(distanceInfo);
		}
		logger.debug("getDistanceList deveui is:{}, listDistance POW is:{}",mdata.getDeveui(), listDistance.toString());
		return listDistance;			
	}
	
	

	//计算距离的平方,节省点计算资源,就不开方了
	private Double getDistance(LocGpsCorrectionConfig config, Map<String, Object> gpsData){
		
		Double x = 0.0;
		Double y = 0.0;
		if(config != null && gpsData != null){
			try{
				x = Double.parseDouble((String)gpsData.get("lon"));
				y = Double.parseDouble((String)gpsData.get("lat"));			
			}catch (Exception e) {
				return -1.0;
			}
			
			Double x0 = config.getX0();
			Double y0 = config.getY0();
			
			//这里求得是坐标的平方和,距离的平方
			return (Math.pow((x - x0),2)  + Math.pow((y - y0),2)); 
		}else{
			return -1.0;
		}	
	}
	
	
	private DistanceInfo getMinDistancePoint(List<DistanceInfo> distanceInfoList){
		if(distanceInfoList == null || distanceInfoList.size() == 0){
			return null;		
		}
		
		List<DistanceInfo> sort = null; 
		
		try{
			sort = distanceInfoList.stream().sorted(Comparator.comparing(DistanceInfo::getDistance)).collect(Collectors.toList());	
		}catch (Exception e) {
			return null;
		}
		
		if(sort != null && sort.size() != 0){
			logger.info("correctGpsWithoutDev getMinDistancePoint, DistanceInfo:{}",sort.get(0));
			return sort.get(0);		
		}else{
			return null;
		}						
	}
	
	
	
	/**
	 * 对于输入的纠偏坐标,计算所在的一组分组,并把周围8个分组一起返回
	 * 分组的key形式:"x,y"  
	 */	
	private List<String> getGroup(Map<String, Object> gpsData,Integer count) throws Exception{
				
		if (gpsData == null) return null;

		Double x = 0.0;
		Double y = 0.0;
		
		try{
			x = Double.parseDouble((String)gpsData.get("lon"));
			y = Double.parseDouble((String)gpsData.get("lat"));			
		}catch (Exception e) {
			
			return null;
		}
			
		Double xGroup = Math.floor(x / groupLen) * groupLen;
		Double yGroup = Math.floor(y / groupLen) * groupLen;
		
		List<String> ret = new ArrayList<String>();
		Double xTemp = 0.0;
		Double yTemp = 0.0;

		//x列上下三组区域
		if(count.equals(1)) //第一次时获取时,中间的区域要参与计算,以后的就只算周围的
			ret.add(xGroup.toString() + "," + yGroup.toString());
								
		yTemp = yGroup - count * groupLen;
		ret.add(xGroup.toString() + "," + yTemp.toString());
		yTemp = yGroup + count * groupLen;	
		ret.add(xGroup.toString() + "," + yTemp.toString());

		//x - groupLen列上下三组区域
		xTemp = xGroup - count * groupLen;	
		ret.add(xTemp.toString() + "," + yGroup.toString());
		yTemp = yGroup - count * groupLen;
		ret.add(xTemp.toString() + "," + yTemp.toString());
		yTemp = yGroup + count * groupLen;	
		ret.add(xTemp.toString() + "," + yTemp.toString());

		//x + groupLen列上下三组区域
		xTemp = xGroup + count * groupLen;
		ret.add(xTemp.toString() + "," + yGroup.toString());
		yTemp = yGroup - count * groupLen;
		ret.add(xTemp.toString() + "," + yTemp.toString());	
		yTemp = yGroup + count * groupLen;	
		ret.add(xTemp.toString() + "," + yTemp.toString());
		
		logger.info("correctGpsWithoutDev getGroup:{}", ret.toString());		
		return ret;
	}
		
		
	
	// 纠偏点信息内部类
	class DistanceInfo{
			
		private Double distance;
		
		private LocGpsCorrectionConfig config;

		public Double getDistance() {
			return distance;
		}

		public void setDistance(Double distance) {
			this.distance = distance;
		}

		public LocGpsCorrectionConfig getConfig() {
			return config;
		}

		public void setConfig(LocGpsCorrectionConfig config) {
			this.config = config;
		}

		@Override
		public String toString() {
			return "DistanceInfo [distance=" + distance + ", config=" + config + "]";
		}
	}

	public Double getGroupLen() {
		return groupLen;
	}

	public void setGroupLen(Double groupLen) {
		this.groupLen = groupLen;
	}
	
	
	
	public void switchGwRssiPubllish(String sw) throws Exception{
		if(sw == null || sw.trim().equals("")){
			this.needPublish = false;
			return;
		}
		if(ON.equalsIgnoreCase(sw.trim())){			
			this.needPublish = true;
			logger.info("gw lora rssi publish is opened");			
		}else if(OFF.equalsIgnoreCase(sw.trim())){			
			this.needPublish = false;
			logger.info("gw lora rssi publish is closed");
		}			
	}
	
	
	
	public boolean getGwPublishStatus() throws Exception{	
		
		return  this.needPublish;
	}
	
}
